/*
 * Copyright (c) 2011-2012 Alexander Dubu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * o  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * o  Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * o  Neither the name Axil nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package axil.framework;

import axil.api.*;
import axil.api.extend.Environment;
import axil.api.extend.Parameters;
import axil.engine.Config;
import axil.engine.Config.Option;
import axil.engine.Repository;
import axil.engine.function.Functor;
import axil.etc.NameValue;
import axil.framework.collection.Pair;
import axil.framework.collection.SafeIterator;
import axil.framework.error.ApplicationException;
import axil.framework.error.InternalException;
import axil.framework.format.SimpleFormatting;
import axil.framework.localization.Locality;
import axil.framework.localization.Message;
import axil.framework.localization.Namespace;
import axil.framework.resource.ResourceManager;
import axil.framework.type.AxilShell;
import axil.framework.type.Sized;
import axil.framework.user.User;
import axil.framework.validation.Validated;
import axil.framework.validation.ValidationContext;
import axil.framework.xml.InputXML;
import axil.stdlib.collection.type.Arry;
import axil.stdlib.collection.type.Dict;
import axil.stdlib.core.type.Bool;
import axil.stdlib.core.type.Nil;
import axil.stdlib.numbers.type.Num;
import axil.stdlib.text.type.Str;
import axil.vm.Env;
import axil.vm.opcode.Relinkable;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.Locale;


/**
 * A collection of convenience functions to ease the excessive typing burden
 * that is Java development. The best way to use these functions to their full
 * effect is to statically import them:
 *
 * 		import static axil.framework.Functions.*;
 *
 * This enables simple expressions (often one-liners) to replace large swathes
 * of traditional Java code. For example, loading a class and invoking a static
 * method on it traditionally involves:
 * <pre>
 *		try
 *		{
 *			Class type = Class.forName("com.foo.Example");
 *			Method method = type.getMethod("getThat");
 *			return method.invoke(null);
 *		}
 *		catch (ClassNotFoundException e)
 *		{
 *			throw new RuntimeException(e);
 *		}
 *		catch (NoSuchMethodException e)
 *		{
 *			throw new RuntimeException(e);
 *		}
 *		catch (IllegalAccessException e)
 *		{
 *			throw new RuntimeException(e);
 *		}
 *		catch (InvocationTargetException e)
 *		{
 *			throw new RuntimeException(e);
 *		}
 *
 * </pre>
 * But, using these functions, those 22 lines of code become simply this:
 * <pre>
 *    	invoke(classof("com.foo.Example"), "getThat")
 * </pre>
 * Conciseness is preferred over verbosity as a philosophical point. Thus, a
 * very common function like "nv(a, b)" is used instead of the more traditional
 * "new NameValue(a, b)" style.
 */
public final class Functions {
	public static final String NIL = "(Nil)";

	private Functions() {}


	//#########################################################################
	// REFLECTION
	//#########################################################################

	/**
	 * Create a new instance of the given type. The type given must have a
	 * public, parameter-less constructor.
	 */
	public static <T> T construct(Class<T> type) {
		try {
			return (T)type.newInstance();
		} catch (InstantiationException e)	{
			throw abort(e);
		} catch (IllegalAccessException e)	{
			throw abort(e);
		}
	}


	/**
	 * Create an instance of the given type (typically an interface) by loading
	 * the class with the given name. For example, to load a specific
	 * implementation of the interface "Foo", use the following:
	 * <pre>
	 *     Foo f = construct(Foo.class, "foo.bar.MyFoo");
	 * </pre>
	 */
	public static <T> T construct(Class<T> type, String name) {
		return (T) construct(classof(name));
	}


	/**
	 * A convenience method to tell if a class implements a specific interface.
	 * This tells if the class or any class it inherits from implements the
	 * given interface.
	 */
	public static boolean implementer(Class type, Class iface) {
		return iface.isAssignableFrom(type);
	}


	public static List<Class<?>> classes(List<String> names) {
		List<Class<?>> list = new ArrayList<Class<?>>(names.size());
		for (String name : names) {
			list.add(classof(name));
		}
		return list;
	}


	public static Class<?> classof(String name) {
		try {
			return Class.forName(name);
		} catch (ClassNotFoundException e) {
			throw abort(e);
		}
	}


	/**
	 * Return a Field object representing a member of the given type. The field
	 * returned has all access controls disabled. An exception is thrown if the
	 * field does not exist in the type.
	 */
	public static Field field(Class<?> type, String name) {
		try {
			Field f = type.getField(name);
			f.setAccessible(true);
			return f;
		} catch (NoSuchFieldException e) {
			throw abort(e);
		}
	}


	/**
	 * Get the value of a static declaration of a field, if one exists. If the
	 * static field does not exist on the class, then a null is returned.
	 */
	public static <T> T declaration(Class<?> type, Class<? extends T> value,
	                                String name) {
		try {
			Field f = type.getField(name);
			if (Modifier.isStatic(f.getModifiers())) {
				if (value.isAssignableFrom(f.getType())) {
					f.setAccessible(true);
					return (T)f.get(null);
				}
			}
		} catch (NoSuchFieldException e) {
			// IGNORED
		} catch (IllegalAccessException e) {
			// IGNORED
		}
		return null;
	}


	public static Method method(Class<?> type, String method, Class<?>... params) {
		try {
			Method m = type.getMethod(method, params);
			m.setAccessible(true);
			return m;
		} catch (NoSuchMethodException e) {
			throw abort(e);
		}
	}


	public static Object invoke(Object self, Method method, Object... params) {
		try {
			return method.invoke(self, params);
		} catch (IllegalAccessException e)	{
			throw abort(e);
		} catch (InvocationTargetException e)	{
			throw abort(e);
		}
	}


	/**
	 * Invoke a static, parameter-less method on the given type.
	 */
	public static Object invoke(Class<?> type, String method) {
		return invoke(type, method(type, method));
	}


	/**
	 * A convenience method for easily setting an object's field value via
	 * reflection. This sets the field in the given object to the given value.
	 * The field given must be a member of the object and the type must be valid
	 * for the value.
	 */
	public static Object setf(Object self, Field field, Object value) {
		try {
			field.set(self, value);
			return value;
		} catch (IllegalAccessException e) {
			throw abort(e);
		}
	}


	public static Object getf(Object object, Field field) {
		try {
			return field.get(object);
		} catch (IllegalAccessException e) {
			throw abort(e);
		}
	}


	//#########################################################################
	// OBJECTS
	//#########################################################################

	/**
	 * Invoke the equals() method on the given two objects. This function
	 * gracefully handles either or both of the arguments being null. If both
	 * arguments are null, then a true value is returned.
	 */
	public static <T> boolean equal(T a, T b) {
		if (a == null) {
			return b == null;
		}
		return b == null ? false : a.equals(b);
	}


	/**
	 * Return a true value if any of the given objects are nil.
	 */
	public static boolean nil(AxilObject... objects) {
		for (AxilObject a : objects) {
			if (a == Nil.object) {
				return true;
			}
		}
		return false;
	}


	public static AxilObject axil(Object value) {
		if (value == null) {
			return Nil.object;
		}
		if (value instanceof AxilObject) {
			return (AxilObject)value;
		}
		AxilType type = Repository.instance().find(value.getClass());
		if (type == null) {
			if (value instanceof Collection) {
				/*
				 * There are many oddball forms of collection around, and we
				 * cannot bind to all of them (since some are inaccessible, such
				 * as EmptyList). So, we handle them manually here.
				 */
				Collection c = (Collection)value;
				if (c.isEmpty()) {
					return Arry.empty;
				}
				return new Arry(c).immute();
			}

			if (value instanceof Map) {
				/*
				 * Likewise for the above, but we must handle maps as a distinct
				 * case since maps are not collections (whomever at Sun thought
				 * that was a good idea should be shot).
				 */
				Map m = (Map)value;
				if (m.isEmpty()) {
					return Dict.empty;
				}
				return new Dict(m).immute();
			}
			return new AxilShell(value);
		}
		return type.transform(value);
	}


	/**
	 * Cast the arbitrary Axil object to a specific type, throwing a friendly,
	 * localized exception if the object is not actually that type. Useful when
	 * you accept only values of a specific type.
	 */
	public static <T extends AxilObject> T cast(Class<T> type, AxilObject value) {
		if (! type.isAssignableFrom(value.getClass())) {
			AxilType t = Repository.instance().find(type);
			if (t == null) {
				throw error(axil(), "unexpected-type",
				            nv("actual", value.type()));
			}
			throw error(axil(), "wrong-type",
			            nv("expected", t),
			            nv("actual", value.type()));
		}
		return (T)value;
	}


	/**
	 * Cast the given object to be a number, or thrown an error indicating the
	 * problem.
	 */
	public static Num num(AxilObject object) {
		try {
			return (Num)object;
		} catch(ClassCastException ex) {
			throw error(axil(), "not-a-number",
			            nv("value", object),
			            nv("type", object.type()));
		}
	}


	/**
	 * Get the definition of the type with the given identity. If there is no
	 * such known type, then an exception is thrown.
	 */
	public static AxilType typeof(String ident) {
		AxilType type = Repository.instance().type(ident);
		if (type == null) {
			throw abort("There is no known type with that name.",
			            nv("name", ident));
		}
		return type;
	}


	/**
	 * If the given value is null or nil, then return the fallback. otherwise
	 * return the value. This is used as a convenience for dealing with optional
	 * parameters to Axil functions.
	 */
	public static AxilObject opt(AxilObject value, AxilObject fallback) {
		return (value == null) || (value == Nil.object) ? fallback : value;
	}


	/**
	 * Ensure that the given object is a functor that can then be invoked via
	 * its call() method. If the given object is not a functor, then a friendly
	 * exception is raised.
	 */
	public static Functor callable(AxilObject object) {
		try {
			assert object != null;
			return (Functor)object;
		} catch(ClassCastException ex) {
			throw error(axil(), "not-a-function", nv("type", object.type()));
		}
	}


	/**
	 * Produce a string representation of the given object in a form that looks
	 * like code. String are double quoted, characters are single quoted, and
	 * the output is always safe.
	 */
	public static String codify(Object value) {
		if (value == null) {
			return "(null)";
		}
		if (value == Nil.object) {
			return Nil.text;
		}
		if (value instanceof String) {
			return '"' + value.toString() + '"';
		}
		if (value instanceof Character) {
			return '\'' + value.toString() + '\'';
		}
		return value.toString();
	}


	//#########################################################################
	// ERRORS
	//#########################################################################

	/**
	 * Like the Java assert keyword, but remains in effect at runtime regardless
	 * of whether assertions are enabled. If the given condition is false, then
	 * an application exception is thrown.
	 *
	 * @throws axil.framework.error.InternalException
	 * 	Throws this internal exception if the condition is not true.
	 */
	public static void assertion(boolean condition) {
		if (! condition) {
			throw abort("Failed assertion");
		}
	}


	/**
	 * Check the validity of the given condition, returning the value if the
	 * condition is true. If the condition is false, then an exception is
	 * raised. This is useful for making assertions that will be preserved at
	 * run time, and doing so in an elegant manner.
	 */
	public static <T> T check(boolean condition, T value) {
		if (! condition) {
			throw abort("Failed assertion");
		}
		return value;
	}


	/**
	 * Require that the given object not be null. An exception is thrown if the
	 * object is null. This tightens up a lot of code, since a null check can be
	 * performed inline instead of requiring a if block. For example, the
	 * following common idiom:
	 * <pre>
	 *     Foo f = getFoo();
	 *     if (f == null)
	 *     {
	 *         throw new RuntimeException("Cannot be null");
	 *     }
	 * </pre>
	 * Can be reduced to just this:
	 * <pre>
	 *     Foo f = require(getFoo());
	 * </pre>
	 * This reduces the syntactic boilerplate code and better exposes the
	 * intention of the code.
	 */
	public static <T> T require(T object) {
		if (object == null) {
			throw abort("This object is required and cannot be null.");
		}
		return object;
	}


	/**
	 * Tell if the given object is valid. The object is not considered valid
	 * if it is null.
	 */
	public static boolean valid(Validated object) {
		if (object == null) {
			return false;
		}
		return validate(object).ok();
	}


	/**
	 * Validate the given object and all of its contents, returning the result
	 * of the validation. Use the ok() method to determine success or failure.
	 */
	public static ValidationContext validate(Validated object) {
		ValidationContext c = new ValidationContext();
		object.validate(c);
		return c;
	}


	/**
	 * Creates an exception with translated content. The content of the
	 * exception is suitable for presentation to the user. For example, assume
	 * that a translation exists with the key "not-a-number" and contains this
	 * translation value:
	 * <pre>
	 *     The value entered for the [--field--] field is not a number.
	 * </pre>
	 * Then this snippet of code will create the exception and save the values
	 * to be used in the template:
	 * <pre>
	 *     throw error(ns, "not-a-number", field));
	 * </pre>
	 * Catching that exception in code later in the application can present that
	 * error in some form of presentation via this mechanism:
	 * <pre>
	 *     catch(AxilException e)
	 *     {
	 *         show(e.getMessage());
	 *     }
	 * </pre>
	 */
	public static ApplicationException error(Namespace ns, String key,
	        NameValue... values) {
		return new ApplicationException(ns, key, values);
	}


	/**
	 * Create an internal exception with the given message and arguments. This
	 * method is for internal errors, not for user-facing errors.
	 */
	public static InternalException abort(Throwable ex, String message,
	                                      NameValue... parts) {
		throw new InternalException(ex, message, parts);
	}


	/**
	 * Create an internal exception with the given message and arguments. This
	 * method is for internal errors, not for user-facing errors.
	 */
	public static InternalException abort(String message, NameValue... parts) {
		throw new InternalException(message, parts);
	}


	/**
	 * Create an internal exception with the given message and arguments. This
	 * method is for internal errors, not for user-facing errors.
	 */
	public static InternalException abort(Exception e) {
		throw new InternalException(e);
	}


	/**
	 * Throws an exception indicating that the method has not been implemented.
	 * This is an intentional programming mechanism to indicate that code has
	 * not been completed.
	 */
	public static InternalException unimplemented() {
		return abort("This method has not been implemented. To date, there " +
		             "has been no request for this feature and this is strictly " +
		             "placeholder logic. Please report this error to the development " +
		             "team if you need this feature.");
	}


	/**
	 * Throws an exception indicating that something that should be impossible
	 * has actually happened. Typically used in the default section of a switch
	 * statement.
	 */
	public static InternalException unreachable() {
		return abort(
		           "An internal safety mechanism has been triggered. This is thought " +
		           "to be unreachable state. Please report this error to the " +
		           "development team.");
	}


	//#########################################################################
	// COLLECTIONS
	//#########################################################################

	public static int size(Collection list) {
		return list == null ? 0 : list.size();
	}


	/**
	 * Get the object of an object that is a container of other objects. In the
	 * brain-damaged Java type system, that can include collections, maps,
	 * and strings.
	 */
	public static int size(Object object) {
		if (object == null) {
			return 0;
		}
		if (object instanceof Collection) {
			return ((Collection)object).size();
		}
		if (object instanceof Sized) {
			return ((Sized)object).size();
		}
		if (object instanceof CharSequence) {
			return ((CharSequence)object).length();
		}
		if (object instanceof Map) {
			return ((Map)object).size();
		}
		throw abort("An object of this type does not appear to have a size.",
		            nv("type", object.getClass()));
	}


	public static <T> Collection<T> immute(Collection<T> objects) {
		return Collections.unmodifiableCollection(objects);
	}


	public static <T> T first(List<T> list) {
		return list.get(0);
	}


	public static <T> T last(T[] array) {
		return array[array.length - 1];
	}


	public static <T> T last(List<T> list) {
		return list.get(list.size() - 1);
	}


	public static <T> T[] join(T[]... arrays) {
		int size = 0;
		for (T[] a : arrays) {
			size = size + a.length;
		}
		T[] x = (T[]) Array.newInstance(arrays[0].getClass().getComponentType(),
		                                size);
		int i = 0;
		for (T[] a : arrays) {
			for (T obj : a) {
				x[i] = obj;
				i++;
			}
		}
		return x;
	}


	public static <T> T[] array(T... values) {
		return values;
	}


	public static <T> T[] array(List<T> list) {
		T[] array = (T[])Array.newInstance(list.get(0).getClass(), list.size());
		return list.toArray(array);
	}


	public static <T> Set<T> set(List<T> list) {
		return new HashSet<T>(list);
	}


	public static <T> Set<T> set(T... objects) {
		return new HashSet<T>(list(objects));
	}


	public static <T> List<T> list(T... objects) {
		final int size = objects.length;
		List<T> list = new ArrayList<T>(size);
		for (int i = 0; i < size; i ++) {
			list.add(objects[i]);
		}
		return list;
	}


	/**
	 * Convert a set into a list. This should not be necessary, but sadly is.
	 * Oh, the joys of Java's deeply broken collections package.
	 */
	public static <T> List<T> list(Set<T> set) {
		ArrayList<T> list = new ArrayList<T>(set.size());
		list.addAll(set);
		return list;
	}


	public static Iterable<AxilObject> iter(AxilObject object) {
		return (Iterable<AxilObject>)object;
	}


	/**
	 * Return an immutable iterator over the collection of elements. The iterator
	 * prevents removal of objects from the collection.
	 */
	public static <T> Iterator<T> over(Collection<T> objects) {
		return new SafeIterator<T>(objects);
	}


	/**
	 * Lookup an object in a map by the given key. If an object cannot be found
	 * by the key, then an exception is thrown. This common idiom replaces this
	 * code:
	 * <pre>
	 *     Foo f = map.get(key);
	 *     if (f == null)
	 *     {
	 *         throw new RuntimeException("Not found");
	 *     }
	 * </pre>
	 * With this much tighter an intentional form:
	 * <pre>
	 *     Foo f = lookup(map, key);
	 * </pre>
	 * Note that the exception thrown contains information about the key that
	 * was being sought.
	 */
	public static <K,T> T lookup(Map<K,T> map, K key) {
		T value = map.get(key);
		if (value == null) {
			throw abort("The expected key cannot be found", nv("key", key));
		}
		return value;
	}


	//#########################################################################
	// STRINGS
	//#########################################################################

	public static char first(String text) {
		return empty(text) ? '\u0000' : text.charAt(0);
	}


	public static char last(String text) {
		return empty(text) ? '\u0000' : text.charAt(text.length() - 1);
	}


	public static String titled(String text) {
		final int size = text.length();
		switch (size) {
		case 0:
			return text;

		case 1:
			return Character.toString(Character.toTitleCase(first(text)));

		default:
			return Character.toTitleCase(first(text)) + text.substring(1);
		}
	}


	/**
	 * Strip the given string of any leading characters of the given type. If
	 * the string contains nothing but the given character, when a blank string
	 * is returned. The string given cannot be null.
	 */
	public static String leading(String text, char strip) {
		final int size = text.length();
		for (int i = 0; i < size; i++) {
			char ch = text.charAt(i);
			if (ch != strip) {
				return i == 0 ? text : text.substring(i);
			}
		}
		return "";
	}


	/**
	 * Pad the start of the given string with the given padding character until
	 * it reaches the given length. If the string is longer than the given
	 * length, then the string given is returned. The string given cannot be
	 * null.
	 */
	public static String left(String text, char pad, int length) {
		final int size = text.length();
		if (size >= length) {
			return text;
		}
		return repeat(pad, length - size) + text;
	}


	/**
	 * Convert the given integer value into a string, padding to the left with
	 * '0' characters until the length is reached.
	 */
	public static String left(int value, int length) {
		String s = Integer.toString(value);
		final int padding = length - s.length();
		if (padding > 0) {
			s = repeat('0', padding) + s;
		}
		return s;
	}


	/**
	 * Pad the end of the given string with the given padding character until
	 * it reaches the given length. If the string is longer than the given
	 * length, then the string given is returned. The string given cannot be
	 * null.
	 */
	public static String right(String text, char pad, int length) {
		final int size = text.length();
		if (size >= length) {
			return text;
		}
		return text + repeat(pad, length - size);
	}


	/**
	 * Given a paragraph of text containing multiple sentences, return only the
	 * first sentence from the paragraph.
	 */
	public static String leader(String paragraph) {
		if ((paragraph == null) || (paragraph.isEmpty())) {
			return Nil.text;
		}
		StringBuilder b = new StringBuilder();
		final int size = paragraph.length();
		int i = 0;
		while(i < size) {
			char ch = paragraph.charAt(i);
			b.append(ch);
			if (ch == '.') {
				if (i == (size - 1)) {
					return b.toString();
				}
				ch = paragraph.charAt(i);
				if (Character.isWhitespace(ch)) {
					return b.toString();
				}
				b.append(ch);
			}
			i++;
		}
		return b.toString();
	}


	/**
	 * Normalize text, eliminating excess tabs, spaces and newlines. This is
	 * useful when given text that may have come from a source that cannot
	 * properly deal with whitespace and embeds the newlines, tabs and spaces
	 * found in the source. This also eliminates any leading and trailing
	 * whitespace, too.
	 */
	public static String normalize(String text) {
		final int size = text.length();
		final StringBuilder b = new StringBuilder(size);
		int i = 0;
		while(i < size) {
			char ch = text.charAt(i);
			i ++;
			if (! in("\r\t\n ", ch)) {
				b.append(ch);
				break;
			}
		}

		int last = 0;
		boolean whitespace = false;
		while(i < size) {
			char ch = text.charAt(i);
			if (in("\r\t\n ", ch)) {
				if (! whitespace) {
					b.append(' ');
				}
				whitespace = true;
			} else {
				b.append(ch);
				last = i;
				whitespace = false;
			}
			i++;
		}
		b.setLength(Math.min(b.length(), last + 1));
		return b.toString();
	}



	/**
	 * Tell if the given character is within the set of characters.
	 */
	public static boolean in(String chars, char ch) {
		return chars.indexOf(ch) >= 0;
	}


	public static List<String> strings(Collection objects) {
		List<String> list = new ArrayList<String>(objects.size());
		for (Object o : objects) {
			list.add(str(o));
		}
		return list;
	}


	public static String single(List<String> text) {
		StringBuilder b = new StringBuilder();
		for (String t : text) {
			if (b.length() > 0) {
				b.append('\n');
			}
			b.append(t);
		}
		return b.toString();
	}


	public static List<String> wrap(String text) {
		return wrap(text, 70);
	}


	public static List<String> wrap(String text, int max) {
		ArrayList<String> list = new ArrayList<String>();
		final int size = text.length();
		int i = 0;
		int length = 0;
		int start = 0;
		int bp = 0;
		while (i < size) {
			char ch = text.charAt(i);
			if (ch == '\r') {
				i ++;
			} else {
				length ++;
				if (length > max) {
					if (bp > 0) {
						list.add(text.substring(start, bp));
						i = bp;
						boolean done = false;
						while(! done && (i < size)) {
							ch = text.charAt(i);
							if (in(" \r\t", ch)) {
								i ++;
							} else {
								done = true;
							}
						}
						start = i;
						length = 0;
						bp = 0;
					}
				} else {
					if (in("- \t", ch)) {
						if (length > 1) {
							bp = i;
						}
					} else {
						if (ch == '\n') {
							list.add(text.substring(start, i));
							start = i + 1;
							length = 0;
							bp = 0;
						}
					}
				}
				i++;
			}
		}
		if (length > 0) {
			list.add(text.substring(start, size));
		}
		return list;
	}


	/**
	 * Given a set of lines (as might be produced by the wrap() function),
	 * return that set with the lines indented by the indicated number of
	 * tab stops.
	 */
	public static List<String> indent(List<String> lines, int indent) {
		String tab = repeat(' ', indent * 4);
		final int size = lines.size();
		for (int i = 0; i < size; i ++) {
			lines.set(i, tab + lines.get(i));
		}
		return lines;
	}


	public static List<String> lines(String content) {
		LineNumberReader in = new LineNumberReader(new StringReader(content));
		List<String> list = new ArrayList<String>();
		while(true) {
			try {
				String line = in.readLine();
				if (line == null) {
					close(in);
					return list;
				}
				list.add(line);
			} catch (IOException e) {
				throw abort(e);
			}
		}
	}


	public static String text(Object object) {
		if (object == null) {
			throw abort("Unexpected null object.");
		}
		return object.toString();
	}


	public static String str(Object object) {
		return object == null ? NIL : object.toString();
	}


	public static String str(int value) {
		return Integer.toString(value);
	}


	public static Str string(String text) {
		return Str.from(text);
	}


	public static Str string(Object object) {
		return object == null ? Str.nul : Str.from(object.toString());
	}


	public static String blank(Object object) {
		return object == null ? "" : object.toString();
	}


	public static String blanks(int length) {
		return repeat(' ', length);
	}


	public static String repeat(char ch, int length) {
		StringBuilder b = new StringBuilder(length);
		for (int i = 0; i < length; i ++) {
			b.append(ch);
		}
		return b.toString();
	}


	/**
	 * Return the given string in its upper case form, with conversion always
	 * performed for the US English locale.
	 */
	public static String upper(String text) {
		return text.toLowerCase(Locale.US);
	}


	/**
	 * Return the given string in its lower case form, with conversion always
	 * performed for the US English locale.
	 */
	public static String lower(String text) {
		return text.toLowerCase(Locale.US);
	}


	public static String prefix(String prefix, String label) {
		return prefix + '-' + label;
	}


	/**
	 * Return the given text with the prefix removed. If the text is an exact
	 * match with the prefix, then an empty string is returned. If the text does
	 * not being with the prefix, then the text is returned unchanged.
	 */
	public static String deprefix(String text, String prefix) {
		final int ts = text.length();
		final int ps = prefix.length();
		if (text.startsWith(prefix)) {
			return ts > ps ? text.substring(ps) : "";
		}
		return text;
	}


	/**
	 * Get the suffix of the given text based upon the separator given. For
	 * example, if the text contains "foo.bar" and "." is passed as the
	 * separator, then "bar" is returned. If the separator does not exist in the
	 * given text, then null is returned.
	 */
	public static String suffix(String text, String separator) {
		int i = text.lastIndexOf(separator);
		return i >= 0 ? text.substring(0, i) : null;
	}


	/**
	 * Remove the given suffix from the given text. If the suffix given is null
	 * or empty, then the original string is returned.
	 */
	public static String desuffix(String text, String suffix) {
		if (empty(suffix)) {
			return text;
		}
		if (! text.endsWith(suffix)) {
			return text;
		}
		return text.substring(0, text.length() - suffix.length());
	}


	public static boolean empty(String text) {
		return text == null || (text.length() == 0);
	}


	public static List<String> split(String text) {
		List<String> list = new ArrayList<String>();
		for (String t : text.split(",")) {
			list.add(t.trim());
		}
		return list;
	}


	public static String separated(List objects) {
		return separated(objects, ", ");
	}


	public static <T> String separated(T[] objects) {
		StringBuilder b = new StringBuilder();
		for (Object object : objects) {
			if (b.length() > 0) {
				b.append(", ");
			}
			b.append(str(object));
		}
		return b.toString();
	}


	public static String separated(List objects, String separator) {
		StringBuilder b = new StringBuilder();
		for (Object object : objects) {
			if (b.length() > 0) {
				b.append(separator);
			}
			b.append(str(object));
		}
		return b.toString();
	}


	public static String quote(char ch) {
		if ((ch < ' ') || (ch > '~')) {
			return "U+" + hex((int)ch, 4);
		}
		return "'" + ch + '\'';
	}


	public static String quote(Object object) {
		if (object == null) {
			return NIL;
		}
		return '\'' + object.toString() + '\'';
	}


	public static String dequote(String text) {
		final int size = text.length();
		if (size > 2) {
			if (((first(text) == '"') || (first(text) == '\'')) &&
			        (last(text) == '"' || last(text) == '\'')) {
				return text.substring(1, size - 1);
			}
		}
		return text;
	}


	public static String stringify(Object object) {
		if (object == null) {
			return "null";
		}
		return '"' + object.toString() + '"';
	}


	public static String summarize(Object object) {
		if (object == null) {
			return NIL;
		}
		String s = object.toString();
		if (s.length() > 20) {
			s = s.substring(0, 20) + "[...]";
		}
		return s;
	}


	/**
	 * Given a numeric value, convert it into a string of hex digits at least
	 * as long as the given length. The string is padded with leading '0'
	 * characters to reach the length. If the numeric value as a hex string is
	 * longer than the given length, then that string is returned and the value
	 * is not truncated.
	 */
	public static String hex(int value, int digits) {
		String s = Integer.toHexString(value);
		final int pad = digits - s.length();
		if (pad > 0) {
			StringBuilder b = new StringBuilder(digits);
			for (int i = 0; i < pad; i ++) {
				b.append('0');
			}
			b.append(s);
			s = b.toString();
		}
		return s;
	}


	/**
	 * Tell if the given string is a valid identifier.
	 */
	public static boolean identifier(String name) {
		if ((name != null) && ! name.isEmpty()) {
			if (name.trim().equals(name)) {
				if (Character.isLetter(name.charAt(0))) {
					final int size = name.length();
					for (int i = 1; i < size; i ++) {
						char ch = name.charAt(i);
						if (! (Character.isLetterOrDigit(ch) ||
						        ("?!-_".indexOf(ch) >= 0))) {
							return false;
						}
					}
					return true;
				}
			}
		}
		return false;
	}


	//#########################################################################
	// NUMBERS
	//#########################################################################

	public static int min(int a, int b) {
		return a < b ? a : b;
	}


	public static int max(int a, int b) {
		return a > b ? a : b;
	}


	public static boolean between(int value, int min, int max) {
		return (value >= min) && (value <= max);
	}


	/**
	 * Tell if the given value is equivalent to zero. This is strictly a
	 * convenience function.
	 */
	public static boolean zilch(Num value) {
		return value.zero() == value;
	}


	//#########################################################################
	// LOCALIZATION
	//#########################################################################

	/**
	 * Get the default persona when no other one is available.
	 */
	public static Persona persona() {
		return system();
	}


	/**
	 * A persona representing the system itself, where the system is always
	 * in the locale "en_US".
	 */
	public static Persona system() {
		return User.system();
	}


	public static Locality loc(Persona persona) {
		return (Locality)persona.locale();
	}


	public static String fmt(Object object) {
		if (object == null) {
			return Nil.text;
		}
		if (object instanceof Formatted) {
			return fmt((Formatted) object);
		}
		return object.toString();
	}


	public static String fmt(Formatted object) {
		return object.format(persona(), SimpleFormatting.object);
	}


	/**
	 * A convenience for the Axil package itself. Nothing to see here, move
	 * along...
	 */
	public static Namespace axil() {
		return Namespace.axil;
	}


	/**
	 * A convenience function for quickly defining a message object inline.
	 * These are heavily used by validation code.
	 */
	public static Message msg(Namespace ns, String key, NameValue... values) {
		return new Message(ns, key, values);
	}


	public static NameValue nv(String name, String value) {
		return new NameValue(name, value);
	}


	public static NameValue nv(String name, Object value) {
		return new NameValue(name, value);
	}


	//#########################################################################
	// FILES
	//#########################################################################

	/**
	 * Construct a File object from a set of parts. The parts can be strings,
	 * other File objects, or anything that can be made into a string.
	 */
	public static File filename(Object... parts) {
		assert(parts.length > 0);
		StringBuilder b = new StringBuilder();
		for (Object object : parts) {
			if (b.length() > 0) {
				b.append(File.separator);
			}
			b.append(text(object));
		}
		return new File(b.toString());
	}


	/**
	 * Check that the required file given is not null and actually exists. An
	 * exception is thrown if the file does not exist. This returns a boolean
	 * value so that this function may be used in an assertion.
	 */
	public static boolean exists(File file) {
		if (file == null) {
			throw abort("Expecting a required file, found null instead");
		}
		if (! file.exists()) {
			throw abort("Required file or directory does not exist",
			            nv("file", file.getAbsolutePath()));
		}
		return true;
	}


	/**
	 * Remove the extension from the given filename. If the file does not
	 * currently have an extension, then the filename is returned. The filename
	 * returned is only then name, not the full path.
	 */
	public static String basename(File file) {
		String name = file.getName();
		final int i = name.lastIndexOf('.');
		if (i >= 0) {
			name = name.substring(0, i);
		}
		return name;
	}


	/**
	 * Remove the current filename extension and replace it with the given
	 * alternate extension, returning a new file object. The file object returned
	 * is not guaranteed to exist, only not be null. The file returned will be
	 * in the same directory as the given file. if the given file does not have
	 * an extension, then the given one is added. The extension given must being
	 * with a "." character.
	 */
	public static File retarget(File file, String extension) {
		assert extension.charAt(0) == '.';
		return filename(path(file), basename(file) + extension);
	}


	/**
	 * Return the string containing the path element for this file. For example,
	 * given a file named "/etc/foo/bar.txt", this function returns the string
	 * "/etc/foo". If this file is localed in the root directory, then just the
	 * "/" is returned.
	 */
	public static String path(File file) {
		String p = file.getAbsolutePath();
		final int i = p.lastIndexOf(File.separator);
		if (i < 0) {
			return File.separator;
		}
		return p.substring(0, i);
	}


	public static Collection<File> files(File directory, String extension) {
		assert directory != null;
		assert first(extension) == '.';

		List<File> list = new ArrayList<File>();
		for (File f : directory.listFiles()) {
			if (f.getName().endsWith(extension)) {
				list.add(f);
			}
		}
		return list;
	}


	/**
	 * Create a new reader for the given file. An exception is thrown if the
	 * file does not exist.
	 */
	public static LineNumberReader reader(File file) {
		try {
			return new LineNumberReader(new BufferedReader(
			                                new InputStreamReader(new FileInputStream(file), "UTF-8")));
		} catch (IOException e) {
			throw abort(e);
		}
	}


	/**
	 * Return the content of the given file as a collection of String objects.
	 * Useful when processing small files, but can present a memory consumption
	 * issue with very large files.
	 */
	public static List<String> load(File file) {
		ArrayList<String> lines = new ArrayList<String>();
		LineNumberReader in = null;
		try {
			in = reader(file);
			boolean end = false;
			while (! end) {
				String line = in.readLine();
				if (line == null) {
					end = true;
				} else {
					lines.add(line);
				}
			}
		} catch (IOException e) {
			throw abort(e);
		} finally {
			close(in);
		}
		return lines;
	}


	public static List<String> entries(File file) {
		try {
			return entries(new FileInputStream(file));
		} catch (FileNotFoundException e) {
			throw abort(e);
		}
	}


	public static List<String> entries(InputStream in) {
		ArrayList<String> entries = new ArrayList<String>();
		LineNumberReader reader = null;
		try {
			reader = new LineNumberReader(new BufferedReader(
			                                  new InputStreamReader(in, "UTF-8")));
			boolean end = false;
			while (! end) {
				String line = reader.readLine();
				if (line == null) {
					end = true;
				} else {
					line = line.trim();
					if (! line.startsWith("#")) {
						entries.add(line);
					}
				}
			}
		} catch(IOException e) {
			throw abort(e);
		} finally {
			close(reader);
		}
		return entries;
	}


	public static void save(File file, List<String> content) {
		PrintWriter out = null;
		try {
			out = new PrintWriter(file, "UTF-8");
			for (String line : content) {
				out.println(line);
			}
		} catch (IOException e) {
			throw abort(e);
		} finally {
			close(out);
		}
	}


	//#########################################################################
	// STREAMS
	//#########################################################################

	public static InputStream resource(Object anchor, String name) {
		InputStream in = anchor.getClass().getResourceAsStream(name);
		if (in == null) {
			throw abort("A resource with that name cannot be found",
			            nv("resource", name));
		}
		return in;
	}


	/**
	 * Close the given input stream. If the given stream is null, then this has
	 * no affect. An exception is thrown if there is an error while attempting
	 * the close.
	 */
	public static void close(Reader in) {
		if (in != null) {
			try {
				in.close();
			} catch (IOException e) {
				throw abort(e);
			}
		}
	}


	/**
	 * Close the given input stream. If the given stream is null, then this has
	 * no affect. An exception is thrown if there is an error while attempting
	 * the close.
	 */
	public static void close(InputStream in) {
		if (in != null) {
			try {
				in.close();
			} catch (IOException e) {
				throw abort(e);
			}
		}
	}


	/**
	 * Close the given output stream. If the given stream is null, then this has
	 * no affect. An exception is thrown if there is an error while attempting
	 * the close.
	 */
	public static void close(Writer out) {
		if (out != null) {
			try {
				out.close();
			} catch (IOException e) {
				throw abort(e);
			}
		}
	}


	//#########################################################################
	// TIME
	//#########################################################################

	/**
	 * Get the current system clock time in a form compatible with the Java
	 * currentTimeMillis() method.
	 */
	public static long now() {
		return System.currentTimeMillis();
	}


	private static final Map<String, Long> units = new HashMap<String, Long>();
	static {
		units.put("ms", 1L);
		units.put("sec", 1000L);
		units.put("min", 60000L);
	}

	public static long duration(String spec) {
		StringBuilder m = new StringBuilder();
		boolean end = false;
		int i = 0;
		while(! end) {
			final char ch = spec.charAt(i);
			if (Character.isDigit(ch)) {
				m.append(ch);
				i ++;
			} else {
				end = true;
			}
		}
		final String unit = spec.substring(i).trim();
		Long conversion = units.get(unit);
		if (conversion == null) {
			abort("Invalid units specified", nv("unit", unit));
		}
		return Long.parseLong(m.toString()) * conversion;
	}


	//#########################################################################
	// DEBUGGING
	//#########################################################################

	public static void debug(Object... values) {
		for (Object value : values) {
			System.out.print(str(value));
		}
		System.out.println();
	}


	public static void debug(int indent, Object value) {
		debug(repeat(' ', 4 * indent) + str(value));
	}


	//#########################################################################
	// SERIALIZATION
	//#########################################################################

	public static <T> T xml(Class<T> type, File file) {
		try {
			FileInputStream in = new FileInputStream(file);
			InputXML doc = InputXML.read(file.getAbsolutePath(), in);
			return doc.root(type);
		} catch (FileNotFoundException e) {
			throw abort(e);
		}
	}


	public static <T> T xml(Class<T> type, String reference, Reader in) {
		InputXML doc = InputXML.read(reference, in);
		return doc.root(type);
	}


	public static <T> T xml(Class<T> type, String reference, String xml) {
		InputXML doc = InputXML.read(reference,
		                             new ByteArrayInputStream(xml.getBytes()));
		return doc.root(type);
	}


	//#########################################################################
	// SCRIPT SAFETY
	//#########################################################################

	/**
	 * Tell if the given environment will allow this execution environment to
	 * grow by this amount.
	 */
	public static void allow(Environment env, Parameters params, int size) {
		((Env)env).allow(params, size);
	}


	//#########################################################################
	// MISCELLANEOUS
	//#########################################################################

	/**
	 * Get the directory where the Axil runtime environment has been installed.
	 * This is used to locate run-time resources for test purposes.
	 */
	public static String home() {
		return ResourceManager.instance().home();
	}


	/**
	 * Get an environment variable with the given key. A JVM command line
	 * argument can also be used to override the environment variable. If the
	 * variable is not set, then an exception is thrown.
	 */
	public static String env(String key) {
		String v = System.getProperty(key);
		if (v == null) {
			v = System.getenv(key);
			if (v == null) {
				throw abort(
				    "The required environment was not set. This must be set " +
				    "either via a '-D' option to the JVM or as an environment " +
				    "variable in the shell invoking this application.",
				    nv("key", key));
			}
		}
		return v;
	}


	/**
	 * Tell if the given option is configured in the default configuration. We
	 * use the default configuration since these options are not accessible via
	 * customized configurations.
	 */
	public static boolean enabled(Option option) {
		return ((Config)Axil.instance().configuration()).enabled(option);
	}


	public static <A extends AxilObject, B extends AxilObject>
	Pair<A,B> pair(A a, B b) {
		return new Pair<A,B>(a, b);
	}


	public static Bool bool(boolean value) {
		return value ? Bool.TRUE : Bool.FALSE;
	}


	public static void replace(AxilObject[] objects, int bad, int good) {
		final int size = objects.length;
		for (int i = 0; i < size; i ++) {
			replace(objects[i], bad, good);
		}
	}


	public static void replace(int[] ids, int bad, int good) {
		final int size = ids.length;
		for (int i = 0; i < size; i ++) {
			if (ids[i] == bad) {
				ids[i] = good;
			}
		}
	}


	public static void replace(AxilObject object, int bad, int good) {
		if (object instanceof Relinkable) {
			((Relinkable)object).relink(bad, good);
		}
	}


	private static int counter = 0;

	public static synchronized int nextID() {
		counter ++;
		return counter;
	}
}
